Celovit vodnik po ključni besedi TypeScript 'infer', ki pojasnjuje, kako jo uporabiti s pogojnimi tipi za zmogljivo izločanje in manipulacijo tipov, vključno z naprednimi primeri uporabe.
Obvladovanje TypeScript Infer: Izločanje pogojnih tipov za napredno manipulacijo tipov
TypeScriptov sistem tipov je neverjetno zmogljiv in omogoča razvijalcem ustvarjanje robustnih in vzdržljivih aplikacij. Ena od ključnih funkcij, ki omogoča to moč, je ključna beseda infer
, ki se uporablja v povezavi s pogojnimi tipi. Ta kombinacija zagotavlja mehanizem za izločanje specifičnih tipov iz kompleksnih tipskih struktur. Ta objava v spletnem dnevniku se poglobi v ključno besedo infer
, pojasnjuje njeno funkcionalnost in prikazuje napredne primere uporabe. Raziskali bomo praktične primere, ki se uporabljajo v različnih scenarijih razvoja programske opreme, od interakcije z API-jem do kompleksne manipulacije podatkovnih struktur.
Kaj so pogojni tipi?
Preden se poglobimo v infer
, hitro preglejmo pogojne tipe. Pogojni tipi v TypeScriptu vam omogočajo, da definirate tip na podlagi pogoja, podobno kot ternarni operator v JavaScriptu. Osnovna sintaksa je:
T extends U ? X : Y
To se bere kot: "Če je tip T
priredljiv tipu U
, potem je tip X
; sicer je tip Y
."
Primer:
type IsString<T> = T extends string ? true : false;
type StringResult = IsString<string>; // type StringResult = true
type NumberResult = IsString<number>; // type NumberResult = false
Predstavitev ključne besede infer
Ključna beseda infer
se uporablja v stavku extends
pogojnega tipa za deklariranje tipa spremenljivke, ki jo je mogoče sklepati iz tipa, ki se preverja. V bistvu vam omogoča, da "zajamete" del tipa za kasnejšo uporabo.
Osnovna sintaksa:
type MyType<T> = T extends (infer U) ? U : never;
V tem primeru bo TypeScript poskušal sklepati tip U
, če je T
priredljiv nekemu tipu. Če je sklepanje uspešno, bo tip U
; sicer bo never
.
Preprosti primeri infer
1. Sklepanje tipa vračanja funkcije
Pogost primer uporabe je sklepanje tipa vračanja funkcije:
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
function add(a: number, b: number): number {
return a + b;
}
type AddReturnType = ReturnType<typeof add>; // type AddReturnType = number
function greet(name: string): string {
return `Hello, ${name}!`;
}
type GreetReturnType = ReturnType<typeof greet>; // type GreetReturnType = string
V tem primeru ReturnType<T>
kot vhod sprejme tip funkcije T
. Preveri, ali je T
priredljiv funkciji, ki sprejema vse argumente in vrača vrednost. Če je, sklepa tip vračanja kot R
in ga vrne. V nasprotnem primeru vrne any
.
2. Sklepanje tipa elementa polja
Drug uporaben scenarij je izločanje tipa elementa iz polja:
type ArrayElementType<T> = T extends (infer U)[] ? U : never;
type NumberArrayType = ArrayElementType<number[]>; // type NumberArrayType = number
type StringArrayType = ArrayElementType<string[]>; // type StringArrayType = string
type MixedArrayType = ArrayElementType<(string | number)[]>; // type MixedArrayType = string | number
type NotAnArrayType = ArrayElementType<number>; // type NotAnArrayType = never
Tukaj ArrayElementType<T>
preveri, ali je T
tip polja. Če je, sklepa tip elementa kot U
in ga vrne. Če ne, vrne never
.
Napredni primeri uporabe infer
1. Sklepanje parametrov konstruktorja
Uporabite lahko infer
za izločanje tipov parametrov funkcije konstruktorja:
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
class Person {
constructor(public name: string, public age: number) {}
}
type PersonConstructorParams = ConstructorParameters<typeof Person>; // type PersonConstructorParams = [string, number]
class Point {
constructor(public x: number, public y: number) {}
}
type PointConstructorParams = ConstructorParameters<typeof Point>; // type PointConstructorParams = [number, number]
V tem primeru ConstructorParameters<T>
sprejme tip funkcije konstruktorja T
. Sklepa tipe parametrov konstruktorja kot P
in jih vrne kot nabor.
2. Izločanje lastnosti iz tipov objektov
infer
se lahko uporablja tudi za izločanje specifičnih lastnosti iz tipov objektov z uporabo tipov mapiranja in pogojnih tipov:
type PickByType<T, K extends keyof T, U> = {
[P in K as T[P] extends U ? P : never]: T[P];
};
interface User {
id: number;
name: string;
age: number;
email: string;
isActive: boolean;
}
type StringProperties = PickByType<User, keyof User, string>; // type StringProperties = { name: string; email: string; }
type NumberProperties = PickByType<User, keyof User, number>; // type NumberProperties = { id: number; age: number; }
//An interface representing geographic coordinates.
interface GeoCoordinates {
latitude: number;
longitude: number;
altitude: number;
country: string;
city: string;
timezone: string;
}
type NumberCoordinateProperties = PickByType<GeoCoordinates, keyof GeoCoordinates, number>; // type NumberCoordinateProperties = { latitude: number; longitude: number; altitude: number; }
Tukaj PickByType<T, K, U>
ustvari nov tip, ki vključuje samo lastnosti T
(s ključi v K
), katerih vrednosti so priredljive tipu U
. Tip mapiranja iterira po ključih T
in pogojni tip filtrira ključe, ki se ne ujemajo z določenim tipom.
3. Delo z obljubami (Promises)
Lahko sklepate o razrešenem tipu Promise
:
type Awaited<T> = T extends Promise<infer U> ? U : T;
async function fetchData(): Promise<string> {
return 'Data from API';
}
type FetchDataType = Awaited<ReturnType<typeof fetchData>>; // type FetchDataType = string
async function fetchNumbers(): Promise<number[]> {
return [1, 2, 3];
}
type FetchedNumbersType = Awaited<ReturnType<typeof fetchNumbers>>; //type FetchedNumbersType = number[]
Tip Awaited<T>
sprejme tip T
, za katerega se pričakuje, da je Promise. Tip nato sklepa razrešeni tip U
obljube in ga vrne. Če T
ni obljuba, vrne T. To je vgrajen tip pomožnega programa v novejših različicah TypeScript.
4. Izločanje tipa polja obljub (Promises)
Kombinacija Awaited
in sklepanje tipa polja vam omogoča, da sklepate tip, razrešen s poljem obljub. To je še posebej koristno pri delu z Promise.all
.
type PromiseArrayReturnType<T extends Promise<any>[]> = {
[K in keyof T]: Awaited<T[K]>;
};
async function getUSDRate(): Promise<number> {
return 0.0069;
}
async function getEURRate(): Promise<number> {
return 0.0064;
}
const rates = [getUSDRate(), getEURRate()];
type RatesType = PromiseArrayReturnType<typeof rates>;
// type RatesType = [number, number]
Ta primer najprej definira dve asinhroni funkciji, getUSDRate
in getEURRate
, ki simulirata pridobivanje menjalnih tečajev. Pomožni tip PromiseArrayReturnType
nato izloči razrešeni tip iz vsake Promise
v polju, kar ima za posledico tip nabora, kjer je vsak element tip, na katerega se čaka ustrezna obljuba.
Praktični primeri v različnih domenah
1. Aplikacija e-trgovine
Upoštevajte aplikacijo e-trgovine, kjer pridobivate podatke o izdelku iz API-ja. Uporabite lahko infer
za izločanje tipa podatkov o izdelku:
interface Product {
id: number;
name: string;
price: number;
description: string;
imageUrl: string;
category: string;
rating: number;
countryOfOrigin: string;
}
async function fetchProduct(productId: number): Promise<Product> {
// Simulacija klica API
return new Promise((resolve) => {
setTimeout(() => {
resolve({
id: productId,
name: 'Example Product',
price: 29.99,
description: 'A sample product',
imageUrl: 'https://example.com/image.jpg',
category: 'Electronics',
rating: 4.5,
countryOfOrigin: 'Canada'
});
}, 500);
});
}
type ProductType = Awaited<ReturnType<typeof fetchProduct>>; // type ProductType = Product
function displayProductDetails(product: ProductType) {
console.log(`Product Name: ${product.name}`);
console.log(`Price: ${product.price} ${product.countryOfOrigin === 'Canada' ? 'CAD' : (product.countryOfOrigin === 'USA' ? 'USD' : 'EUR')}`);
}
fetchProduct(123).then(displayProductDetails);
V tem primeru definiramo vmesnik Product
in funkcijo fetchProduct
, ki pridobiva podatke o izdelku iz API-ja. Uporabljamo Awaited
in ReturnType
za izločanje tipa Product
iz povratnega tipa funkcije fetchProduct
, kar nam omogoča tipsko preverjanje funkcije displayProductDetails
.
2. Internacionalizacija (i18n)
Recimo, da imate funkcijo za prevajanje, ki vrne različne nize glede na jezikovno okolje. Uporabite lahko infer
za izločanje povratnega tipa te funkcije za varnost tipa:
interface Translations {
greeting: string;
farewell: string;
welcomeMessage: (name: string) => string;
}
const enTranslations: Translations = {
greeting: 'Hello',
farewell: 'Goodbye',
welcomeMessage: (name: string) => `Welcome, ${name}!`,
};
const frTranslations: Translations = {
greeting: 'Bonjour',
farewell: 'Au revoir',
welcomeMessage: (name: string) => `Bienvenue, ${name}!`,
};
function getTranslation(locale: 'en' | 'fr'): Translations {
return locale === 'en' ? enTranslations : frTranslations;
}
type TranslationType = ReturnType<typeof getTranslation>;
function greetUser(locale: 'en' | 'fr', name: string) {
const translations = getTranslation(locale);
console.log(translations.welcomeMessage(name));
}
greetUser('fr', 'Jean'); // Output: Bienvenue, Jean!
Tukaj se TranslationType
sklepa, da je vmesnik Translations
, kar zagotavlja, da ima funkcija greetUser
pravilne informacije o tipu za dostop do prevedenih nizov.
3. Ravnanje z odgovori API
Pri delu z API-ji je struktura odgovora lahko zapletena. infer
lahko pomaga pri izločanju specifičnih tipov podatkov iz gnezdenih odgovorov API:
interface ApiResponse<T> {
status: number;
data: T;
message?: string;
}
interface UserData {
id: number;
username: string;
email: string;
profile: {
firstName: string;
lastName: string;
country: string;
language: string;
}
}
async function fetchUser(userId: number): Promise<ApiResponse<UserData>> {
// Simulacija klica API
return new Promise((resolve) => {
setTimeout(() => {
resolve({
status: 200,
data: {
id: userId,
username: 'johndoe',
email: 'john.doe@example.com',
profile: {
firstName: 'John',
lastName: 'Doe',
country: 'USA',
language: 'en'
}
}
});
}, 500);
});
}
type UserApiResponse = Awaited<ReturnType<typeof fetchUser>>;
type UserProfileType = UserApiResponse['data']['profile'];
function displayUserProfile(profile: UserProfileType) {
console.log(`Name: ${profile.firstName} ${profile.lastName}`);
console.log(`Country: ${profile.country}`);
}
fetchUser(123).then((response) => {
if (response.status === 200) {
displayUserProfile(response.data.profile);
}
});
V tem primeru definiramo vmesnik ApiResponse
in vmesnik UserData
. Uporabljamo infer
in indeksiranje tipa, da izločimo UserProfileType
iz odgovora API, s čimer zagotovimo, da funkcija displayUserProfile
prejme pravilen tip.
Najboljše prakse za uporabo infer
- Naj bo preprosto: Uporabljajte
infer
samo, kadar je potrebno. Prekomerna uporaba lahko oteži branje in razumevanje kode. - Dokumentirajte svoje tipe: Dodajte komentarje, da razložite, kaj delajo vaši pogojni tipi in stavki
infer
. - Testirajte svoje tipe: Uporabite tipsko preverjanje TypeScript, da zagotovite, da se vaši tipi obnašajo po pričakovanjih.
- Upoštevajte zmogljivost: Zapleteni pogojni tipi lahko včasih vplivajo na čas prevajanja. Bodite pozorni na kompleksnost svojih tipov.
- Uporabite utilitarne tipe: TypeScript ponuja več vgrajenih utilitarnih tipov (npr.
ReturnType
,Awaited
), ki lahko poenostavijo vašo kodo in zmanjšajo potrebo po prilagojenih stavkihinfer
.
Pogoste pasti
- Nepravilno sklepanje: Včasih lahko TypeScript sklepa tip, ki ni tisto, kar pričakujete. Ponovno preverite svoje definicije tipa in pogoje.
- Ciklične odvisnosti: Bodite previdni pri določanju rekurzivnih tipov z uporabo
infer
, saj lahko vodijo do cikličnih odvisnosti in napak pri prevajanju. - Preveč kompleksni tipi: Izogibajte se ustvarjanju preveč kompleksnih pogojnih tipov, ki jih je težko razumeti in vzdrževati. Razbijte jih na manjše, bolj obvladljive tipe.
Alternative za infer
Čeprav je infer
zmogljivo orodje, obstajajo situacije, kjer so primernejši alternativni pristopi:
- Trditve tipa: V nekaterih primerih lahko uporabite trditve tipa, da izrecno določite tip vrednosti namesto da ga sklepate. Vendar bodite previdni pri trditvah tipa, saj lahko zaobidejo tipsko preverjanje.
- Varovala tipa: Varovala tipa se lahko uporabijo za zožitev tipa vrednosti na podlagi preverjanj v času izvajanja. To je uporabno, ko morate obravnavati različne tipe na podlagi pogojev v času izvajanja.
- Utilitarni tipi: TypeScript ponuja bogat nabor utilitarnih tipov, ki lahko obravnavajo številne pogoste naloge manipulacije tipov, ne da bi potrebovali stavke po meri
infer
.
Zaključek
Ključna beseda infer
v TypeScriptu v kombinaciji s pogojnimi tipi sprošča napredne zmožnosti manipulacije tipov. Omogoča vam izločanje specifičnih tipov iz kompleksnih tipskih struktur, kar vam omogoča pisanje bolj robustne, vzdržljive in tipsko varne kode. Od sklepanja tipov vračanja funkcij do izločanja lastnosti iz tipov objektov so možnosti obsežne. Z razumevanjem načel in najboljših praks, opisanih v tem priročniku, lahko izkoristite infer
v celoti in izboljšate svoje veščine TypeScript. Ne pozabite dokumentirati svojih tipov, jih temeljito preizkusiti in po potrebi razmisliti o alternativnih pristopih. Obvladovanje infer
vam omogoča, da napišete resnično izrazito in zmogljivo kodo TypeScript, kar na koncu vodi do boljše programske opreme.